package com.dc.dbus.session.server;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map.Entry;
import java.util.concurrent.ExecutorService;

import com.dc.dbus.session.annotation.Areq;
import com.dc.dbus.session.annotation.Mreq;
import com.dc.dbus.session.annotation.ReqType;
import com.dc.dbus.session.annotation.Sreq;
import com.dc.dbus.session.bean.FutureResult;
import com.dc.dbus.session.listener.ServerSideEventListener;
import com.dc.dbus.session.register.ServiceRegister;
import com.dc.dbus.transport.commons.ServerSideTransport;

public class Server {
	
	protected final ServiceRegister serviceRegister;
	
	protected final ServerSideTransport serverSideTansport;
	
	protected final ServerStub serverStub;
	
	public Server(ServiceRegister serviceRegister,
			ServerSideTransport serverSideTansport, ExecutorService executor, 
			ServerSideEventListener eventListener) {
		
		this.serviceRegister = serviceRegister;
		this.serverSideTansport = serverSideTansport;
		
		DefaultServerStub defaultServerStub = new DefaultServerStub(serverSideTansport, executor, eventListener);
		this.serverStub = defaultServerStub;
		
		serverSideTansport.startServerSideReceiver(defaultServerStub);
	}
	
//	public void startServerSideReceiver(ServerSideTransport serverSideTansport) {
//		serverSideTansport.startServerSideReceiver((DefaultServerStub)serverStub);
//	}
	
	public void close() {
		
		serverStub.close();
	}
	
	public <T> void registerProducter(String[] routers,
			Class<T> interfaceClass, T instance) {
		
		this.registerProducter(interfaceClass.getName(), routers, interfaceClass, instance);
	}
	
	public <T> void registerProducter(String serviceName, String[] routers,
			Class<T> interfaceClass, T instance) {
		
		if (!interfaceClass.isInterface()) {
			throw new RuntimeException(
					"param 'interfaceClass' must be interface");
		}
		
		HashMap<Integer, CmdInfo> cmdNameMapInfo = new HashMap<Integer, CmdInfo>();
		HashSet<String> methodNameSet = new HashSet<String>();
		
		Method[] methods = interfaceClass.getMethods();
		for (Method m : methods) {
			
			Annotation[] methodAnnotations = m.getAnnotations();
			ReqType reqType = null;
			int cmd = 0;
			for (Annotation a : methodAnnotations) {
				if (a instanceof Sreq) {
					reqType = ReqType.SYNC;
					cmd = ((Sreq) a).value();
					break;
				} else if (a instanceof Areq) {
					reqType = ReqType.ASYNC;
					cmd = ((Areq) a).value();
					if (!m.getReturnType().isAssignableFrom(FutureResult.class)) {
						// 声明为异步的方法返回类型必须为FutureResult<xxx>
						throw new RuntimeException(
								"interface(" + interfaceClass.getName() + ") method(" + m.getName() 
								+ ") is decalare by Areq, the return type must be FutureResult<>");
					}
					break;
				} else if (a instanceof Mreq) {
					reqType = ReqType.MSG;
					cmd = ((Mreq) a).value();
					if (m.getReturnType() != void.class) {
						// 声明为消息的方法不能有返回参数
						throw new RuntimeException(
								"interface(" + interfaceClass.getName() + ") method(" + m.getName() 
								+ ") is decalare by Mreq, must not have return type(return type should be void)");
					}
					break;
				}
			}
			
			if (reqType == null) {
				// 接口的所有方法都必须声明 请求类型('Sreq' or 'Areq' or 'Mreq')
				throw new RuntimeException(
						"interface(" + interfaceClass.getName() + ") method(" + m.getName() + ") must have req Annotation('Sreq' or 'Areq' or 'Mreq')");
			}
			
			if (!methodNameSet.add(m.getName())) {
				// 接口中不允许有同名的方法
				throw new RuntimeException(
						"interface(" + interfaceClass.getName() + ") exits the same method name(" + m.getName() + "), is not allow");
			}
			
			CmdInfo info = new CmdInfo(reqType, instance, m);
			if (cmdNameMapInfo.put(cmd, info) != null) {
				// 接口中有不同方法使用相同的cmd
				throw new RuntimeException(
						"interface(" + interfaceClass.getName() + ") method(" + m.getName() + ")'s cmd(" + cmd + ") is same with other method");
			}
			
		}
		methodNameSet = null;
		
		for (Entry<Integer,CmdInfo> entry : cmdNameMapInfo.entrySet()) {
			serverStub.registerInvoker(entry.getKey(), entry.getValue());
		}
		
		serviceRegister.registerService(serviceName, routers);
	}
}
